package com.gitee.l0km.codegen.thrift;

import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.facebook.swift.codec.metadata.ThriftFieldMetadata;
import com.facebook.swift.codec.metadata.ThriftStructMetadata;
import com.facebook.swift.service.metadata.ThriftMethodMetadata;
import com.gitee.l0km.codegen.annotations.AnnotationException;
import com.gitee.l0km.codegen.annotations.AnnotationRuntimeException;
import com.gitee.l0km.codegen.annotations.DeriveMethod;
import com.gitee.l0km.codegen.annotations.ServicePort;
import com.gitee.l0km.codegen.base.AnnotationUtils;
import com.gitee.l0km.codegen.base.InvalidAnnotationDefineException;
import com.gitee.l0km.codegen.base.InvalidNameException;
import com.gitee.l0km.codegen.base.Method;
import com.gitee.l0km.codegen.base.NewSourceInfoAbstract;
import com.gitee.l0km.codegen.base.ServiceInfo;
import com.gitee.l0km.codegen.base.Method.Parameter;
import com.gitee.l0km.codegen.base.generator.GeneratorUtils;
import com.gitee.l0km.codegen.thrift.ThriftServiceDecoratorConfiguration.TaskType;
import com.gitee.l0km.com4j.base.NameStringUtils;
import com.gitee.l0km.xthrift.base.BaseThriftUtils;
import com.gitee.l0km.xthrift.base.exception.ServiceRuntimeException;
import com.gitee.l0km.xthrift.base.metadata.DecoratorThriftFieldMetadata;
import com.gitee.l0km.xthrift.base.metadata.ErpcType;
import com.gitee.l0km.xthrift.base.metadata.ThriftCatalogWithTransformer;
import com.gitee.l0km.xthrift.thrift.metadata.DecoratorThriftServiceMetadata;
import com.google.common.base.Function;
import com.google.common.base.MoreObjects;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.reflect.TypeToken;

import static com.google.common.base.Preconditions.*;

/**
 * thrift service 生成{@code NewSourceInfo}实现
 * @author guyadong
 *
 * @param <T>
 */
public class ThriftServiceDecorator<T> extends NewSourceInfoAbstract<T> implements ThriftConstants {
	private static final Logger logger = LoggerFactory.getLogger(ThriftServiceDecorator.class);
	private ServiceInfo serviceInfo;
	private final BiMap<Method,String> serviceMethods = HashBiMap.create();
	private final TypeHelper typeHelper = new TypeHelper(this);
	private String generatePackage;
	private String clientInterfaceName;
	private Boolean isUseStringInService = null;
	private Boolean isUseBinaryInService = null;
	private Boolean isUseVectorInService = null;
	private Boolean isUseExceptionInService = null;
	/**
	 * 记录接口中用到的所有容器类型List,Map,Set
	 */
	private final Set<CxxTypeMeta> containerTypes = Sets.newHashSet(
			CxxType.getThriftType(BaseThriftUtils.mapToken(TypeToken.of(String.class), TypeToken.of(String.class)).getType()).getStubType());
	
	private final Set<ErpcType> collectionTypes =  Sets.newLinkedHashSet();
	private final Predicate<Class<?>> findMap = new Predicate<Class<?>>(){

		@Override
		public boolean apply(Class<?> input) {
			return null == input ? false :Map.class.isAssignableFrom(input);
		}};
	private final Predicate<Class<?>> findSet = new Predicate<Class<?>>(){

		@Override
		public boolean apply(Class<?> input) {
			return null == input ? false :Set.class.isAssignableFrom(input);
		}};
	private final CxxType cxxType;
	private final CxxTypeMeta stubCxxTypeMeta;
	private final String thriftClientPackage;
	private final Predicate<Method> tagsFilter;
	private volatile DecoratorThriftServiceMetadata thriftServiceMetadata;
	private Class<?> thriftServiceClass;
	public ThriftServiceDecorator(Class<T> interfaceClass, Class<? extends T> refClass) {
		super(interfaceClass, refClass, null);
		TypeHelper.VERIFYTYPE_MONITOR.set(new Predicate<Type>(){

		@Override
		public boolean apply(Type input) {
			if(null ==isUseVectorInService){
				Class<?> rawType = TypeToken.of(input).getRawType();
				if((rawType.isArray() && rawType.getComponentType()!=byte.class) || List.class.isAssignableFrom(rawType)){
					isUseVectorInService = Boolean.TRUE;	
				}
			}
			if(input instanceof Class<?>){
				if(null == isUseStringInService){
					if(GeneratorUtils.isBinary(input)){
						isUseStringInService = Boolean.TRUE;
					}
				}
				if(null ==isUseBinaryInService && TaskType.ERPC_PROXY.equals(ThriftServiceDecoratorConfiguration.INSTANCE.getTaskType())){
					if(GeneratorUtils.isBinary(input)){
						isUseBinaryInService = Boolean.TRUE;
					}
				}
				if(null ==isUseExceptionInService){
					if(Exception.class.isAssignableFrom((Class<?>) input)){
						isUseExceptionInService = Boolean.TRUE;	
					}
				}
			}
			try{
				CxxTypeMeta meta = CxxType.getThriftType(input).getUiType();
				if(meta.isContainer()){
					containerTypes.add(meta);
				}
			}catch(Exception e){
				logger.debug(e.getMessage());
			}
			return false;
		}});
		thriftClientPackage = ThriftServiceDecoratorConfiguration.INSTANCE.getThriftClientPackage();
		clientInterfaceName = thriftClientPackage + "." + this.interfaceClass.getSimpleName();
		cxxType = CxxType.struct(interfaceClass);
		stubCxxTypeMeta = cxxType.getStubType().cast("::"+CxxHelper.cxxNamespace(clientInterfaceName,true));
		tagsFilter = new Predicate<Method>(){
			@Override
			public boolean apply(Method input) {
				return input.tagsMatched(ThriftServiceDecoratorConfiguration.INSTANCE.getReqiredTags());
			}};
	}

	@Override
	public boolean compile() {
		boolean compileOk=false;
		try{
			super.compile();
			serviceInfo=new ServiceInfo(AnnotationUtils.getServiceAnnotation(interfaceClass));
			for (Method method : GeneratorUtils.sortBySignature(methodsNeedGenerated)) {
				compile(method);
			}
			addImportedClass(ServiceRuntimeException.class);
			compileOk = true;
			TypeHelper.VERIFYTYPE_MONITOR.remove();
		} catch (AnnotationException e) {
			logger.error(e.toString());
		} catch(AnnotationRuntimeException e){
			logger.error(e.toString());
		} 
		return compileOk;
	}
	private final void compile(Method method) throws InvalidNameException {
		TypeHelper.checkNotGeneric(method);
		checkPortSuffix(method);
		String methodName = method.getName()+ methodSuffix(method);
//		net.gdface.utils.SimpleLog.log("portName:{}",methodName);
//		net.gdface.utils.SimpleLog.log("             └─{}",method);
		try {
			serviceMethods.put(method,methodName);
		} catch (IllegalArgumentException e) {
			/** 方法重名抛出异常 */
			throw new InvalidNameException(String.format("duplicated method name %s", methodName));
		}
		typeHelper.checkType(method);
		typeHelper.addReferTypes(method);
		if(NameStringUtils.isThriftReserved( methodName)){
			logger.warn("portName(method name) '{}' of '{}' is thrift IDL reserved word", methodName,this.getInterfaceClass().getName());
		}
		for (Parameter param : method.getParameters()) {
			if(NameStringUtils.isThriftReserved( param.getName())){
				logger.warn("parameter name '{}' of method '{}' is thrift IDL reserved word", param.getName(),methodName);
			}
		}
	}

	private static void checkPortSuffix(Method method) throws InvalidNameException{
		ServicePort servicePort = method.getAnnotation(ServicePort.class);
		if (null != servicePort) {
			// 检查名字是否合法，不允许包含空格，只能是数字字母下划线
			if (!(servicePort.suffix().isEmpty() || servicePort.suffix().matches(AnnotationUtils.PATTERN_NOT_BLANK))){
				throw new InvalidNameException(
						String.format("the suffix of annotation [%s] in method %s of %s must not have space char ",
								ServicePort.class.getSimpleName(), 
								method.getName(), 
								method.getDeclaringClass().getSimpleName()));
			}
		}
	}
	
	@Override
	protected void checkClass(Class<T> interfaceClass, Class<? extends T> refClass, Class<? extends T> baseClass) {
		checkNotNull(interfaceClass,"interfaceClass is null");
		if (!interfaceClass.isInterface()){
			throw new IllegalArgumentException("interfaceClass must be Interface(必须是接口)");
		}
		TypeHelper.checkNotGeneric(interfaceClass);
		if(null != refClass ){
			if ( !interfaceClass.isAssignableFrom(refClass) 
					|| refClass.isInterface()){
				throw new IllegalArgumentException(String.format(
						"refClass must  implement of [%s] (必须实现接口)", interfaceClass.getName()));
			}
			if (!isFullImplemented(interfaceClass,refClass)){
				logger.warn("{} not  implement all methods of [{}] (没有实现接口所有方法)",
						refClass.getName(),
						interfaceClass.getName());
			}
		}
	}

	@Override
	protected void createMethodsNeedGenerated() {
		List<Method> methods = Lists.transform(Arrays.asList(interfaceClass.getMethods()), 
				new Function<java.lang.reflect.Method, Method>() {
					@Override
					public Method apply(java.lang.reflect.Method input) {
						return new Method(input,
								paramTable.getParameterNamesUnchecked(input.getName(), input.getParameterTypes()));
					}
		});
		
		// tags 过滤
		Iterable<Method> filteredByTags = Iterables.filter(methods, tagsFilter);
		ArrayList<Method> filtered = 
				Lists.newArrayList(
						Iterables.filter(filteredByTags, Predicates.compose(new MethodFilter(interfaceClass), Method.TO_REFLECT_METHOD)));
		methodsNeedGenerated.addAll(filtered);
	}

	@Override
	protected void addImportedClassFromMethod(Method method, 
			boolean importExceptions, boolean importReturn, boolean importAnnotation) {
		super.addImportedClassFromMethod(method, importExceptions, importReturn, importAnnotation);
		addImportedClass(getInterfaceClass());
		if(importAnnotation) {
			// 将方法中的注释引用的类添加到import class中
			addImportedClass(getAnnHelper().annotationClassOf(method, false, "SUPERCLASS"));
			addImportedClass(getAnnHelper().annotationClassOf(getInterfaceClass(), "SUPERCLASS"));
		}
	}

	public ServiceInfo getServiceInfo() {
		return serviceInfo;
	}
	public boolean isTargetType(Class<?> type){
		return serviceInfo.getTargetType()==type;
	}
	public List<ThriftStructDecorator> getDecorateTypes() {
		return typeHelper.getDecorateTypes();
	}
	public List<ThriftStructDecorator> getThriftTypes() {
		return typeHelper.getThriftTypes();
	}
	public String toThriftType(Type type) {
		return typeHelper.toThriftType(type);
	}
	public String toClientThriftType(Type type) {
		return typeHelper.toClientThriftType(type);
	}
	
	public String toThriftDecoratorType(Type type) {
		return typeHelper.toThriftDecoratorType(type);
	}

	public String toClientThriftyType(Type type) {
		return typeHelper.toClientThriftyType(type);
	}
	public boolean isClientThriftType(Type type) {
		return typeHelper.isClientThriftType(type);
	}

	public String toThriftyDecoratorType(Type type) {
		return typeHelper.toThriftyDecoratorType(type);
	}

	public boolean sameWithClientThriftType(Type type) {
		return typeHelper.sameWithClientThriftType(type);
	}

	public boolean sameWithClientThriftyType(Type type) {
		return typeHelper.sameWithClientThriftyType(type);
	}

	public boolean sameWithThriftDecoratorType(Type type) {
		return typeHelper.sameWithThriftDecoratorType(type);
	}

	public boolean sameWithThriftyDecoratorType(Type type) {
		return typeHelper.sameWithThriftyDecoratorType(type);
	}

	public String getClientInterfaceName(){
		return clientInterfaceName;
	}
	public String getPortName(Method method){
		ServicePort servicePort = method.getAnnotation(ServicePort.class);
		String portName = method.getName();
		if (null != servicePort) {
			portName += servicePort.suffix();
		}
		return portName;
	}

	public String getGeneratePackage() {
		return generatePackage;
	}

	public void setGeneratePackage(String generatePackage) {
		this.generatePackage = generatePackage;
	}
	public void removeDecorateTypesFromImports(){
		// 排除枚举类型
		Iterable<Class<?>> it = Iterables.filter(typeHelper.getTypesWithDecorator(),new Predicate<Class<?>>() {
			@Override
			public boolean apply(Class<?> input) {
				return !input.isEnum();
			}
		});
		ArrayList<Class<?>> types = Lists.newArrayList(it);
		this.removeClassFromImports(types);
	}
	public void removeExceptionsFromImports(){
		this.removeClassFromImports(typeHelper.getReferExceptions());
	}

	public List<Class<?>> getReferExceptions() {
		return typeHelper.getReferExceptions();
	}
	public boolean isUseStringInService(){
		return Boolean.TRUE.equals(isUseStringInService);
	}
	public boolean isUseBinaryInService(){
		return Boolean.TRUE.equals(isUseBinaryInService);
	}
	public boolean isUseMapInService(){
		return Iterables.tryFind(getImportedList().values(), findMap ).isPresent();
	}
	public boolean isUseSetInService(){
		return Iterables.tryFind(getImportedList().values(), findSet).isPresent();
	}
	public boolean isUseVectorInService(){
		return Boolean.TRUE.equals(isUseVectorInService);
	}
	public boolean isUseExceptionInService(){
		return Boolean.TRUE.equals(isUseExceptionInService);
	}
	/**
	 * 以接口名为基础创建不同后缀的{@link CxxTypeMeta}
	 * @param suffix
	 */
	public CxxTypeMeta makeStubCxxTypeMetaBySuffix(String suffix){
		suffix = MoreObjects.firstNonNull(suffix, "");
		return stubCxxTypeMeta.castName(this.getInterfaceClass().getSimpleName()+suffix);
	}

	public CxxType getCxxType() {
		return cxxType;
	}

	public CxxTypeMeta getStubCxxTypeMeta() {
		return stubCxxTypeMeta;
	}

	public String getThriftClientNamespace() {
		return "::"+CxxHelper.cxxNamespace(thriftClientPackage);
	}

	public Set<CxxTypeMeta> getContainerTypes() {
		return containerTypes;
	}
	public DeriveMethod getDeriveMethodAnnotation(Method method) {
		try {
			return AnnotationUtils.getDeriveMethodAnnotation(method, serviceInfo);
		} catch (InvalidNameException e) {
			throw new RuntimeException(e);
		} catch (InvalidAnnotationDefineException e) {
			throw new RuntimeException(e);
		}
	}
	/**
	 * 返回 {@link DeriveMethod} 注释指定的方法名后缀,没有则返回空
	 * @param method
	 */
	public String methodSuffix(Method method){
		DeriveMethod deriveMethod = getDeriveMethodAnnotation(method);
		if(deriveMethod !=null ){
			String[] suffixs = deriveMethod.methodSuffix();
			return suffixs != null && suffixs.length>0 ? suffixs[0]:"";
		}else {
			ServicePort sp = method.getAnnotation(ServicePort.class);
			if(sp != null){
				return sp.suffix();
			}
		}
		return "";
	}

	public DecoratorThriftServiceMetadata getThriftServiceMetadata(){
		if(thriftServiceMetadata == null){
			synchronized (this) {
				if(thriftServiceMetadata == null){
					thriftServiceMetadata = new DecoratorThriftServiceMetadata(checkNotNull(thriftServiceClass,"thriftServiceClass is uninitialized"),
							ThriftCatalogWithTransformer.CATALOG,
							ThriftServiceDecoratorConfiguration.INSTANCE.getExcludeMethods().get(getInterfaceClass()),
							ThriftServiceDecoratorConfiguration.INSTANCE.getIncludeMethods().get(getInterfaceClass()));
				}
			}
		}
		return thriftServiceMetadata;
	}
	
	public ThriftMethodMetadata getThriftMethodMetadata(Method method){
		if(method != null){
			String name = method.getName() + methodSuffix(method);
			return getThriftServiceMetadata().getMethod(name);
		}
		return null;
	}
	public List<ThriftFieldMetadata> getParameterMetadata(Method method){
		ThriftMethodMetadata methodMetadata = getThriftMethodMetadata(method);
		if(methodMetadata != null){
			return methodMetadata.getParameters();
		}
		return ImmutableList.of();
	}

	/**
	 * @return thriftServiceClass
	 */
	public Class<?> getThriftServiceClass() {
		return thriftServiceClass;
	}

	/**
	 * @param thriftServiceClass 要设置的 thriftServiceClass
	 * @return 当前对象
	 */
	public ThriftServiceDecorator<T> setThriftServiceClass(Class<?> thriftServiceClass) {
		this.thriftServiceClass = thriftServiceClass;
		return this;
	}
	
	public ThriftServiceDecorator<T> erpcProxyInit(){
		if(TaskType.ERPC_PROXY.equals(ThriftServiceDecoratorConfiguration.INSTANCE.getTaskType())){
			DecoratorThriftServiceMetadata metadata = getThriftServiceMetadata();
			for(ThriftMethodMetadata methodMetadata:metadata.getMethods().values()){
				for(ThriftFieldMetadata param:methodMetadata.getParameters()){
					addIfCollection(param);
				}
				if(GeneratorUtils.isCollection(methodMetadata.getReturnType().getJavaType())){
					collectionTypes.add(ErpcType.getErpcType(methodMetadata.getReturnType()));
				}
				updateCollectionTypesForField(getThriftTypes());
				updateCollectionTypesForField(getDecorateTypes());
			}	
		}
		return this;
	}

	/**
	 * 遍历结构类型的成员类型，如果为集合类型(Collecion)则添加到{@link #collectionTypes}
	 * @param input
	 */
	private void updateCollectionTypesForField(List<ThriftStructDecorator> input){
		for(ThriftStructDecorator struct : input){
			Class<?> clazz = struct.getThriftClass();
			if(clazz != null){
				ThriftStructMetadata structMetadata = ThriftCatalogWithTransformer.CATALOG.getThriftStructMetadata(clazz);
				for(ThriftFieldMetadata field : structMetadata.getFields()){
					addIfCollection(field);
				}
			}
		}
	}
	
	private void addIfCollection(ThriftFieldMetadata input){
		if(GeneratorUtils.isCollection(input.getThriftType().getJavaType())){
			collectionTypes.add(((DecoratorThriftFieldMetadata) input).getErpcType());
		}
	}
	/**
	 * @return collectionTypes
	 */
	public List<ErpcType> getCollectionTypes() {
		return Lists.newArrayList(collectionTypes);
	}
	public boolean isNeedTransformer(){
		return typeHelper.needTransformer();
	}

}
